package code;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

public class PhotoAlbum extends DateBasedFile implements IPhotoAlbum {
	public static DateFormat	yyyyMMddUnder	= new DateFormat("yyyy_MM_dd", "\\d{4}_\\d{1,2}+_\\d{1,2}+.*");
	public static DateFormat	yyyyMMddDash	= new DateFormat("yyyy-MM-dd", "\\d{4}-\\d{1,2}+-\\d{1,2}+.*");

	/**
	 * Creates and returns pre-defined list of alternate formats.
	 */
	private static ArrayList<DateFormat> createAlternateFormats() {
		ArrayList<DateFormat> alternates = new ArrayList<DateFormat>();

		alternates.add(yyyyMMddUnder);
		alternates.add(yyyyMMddDash);
		alternates.add(new DateFormat("yyyy_MM", "\\d{4}_\\d{1,2}+.*"));
		alternates.add(new DateFormat("yyyy_MM", "\\d{4}-\\d{1,2}+.*"));
		alternates.add(new DateFormat("yyyyMM", "\\d{4}\\d{1,2}+.*"));

		return alternates;
	}

	/**
	 * Get a list of conflicts with filenames between all of the photos in the Album and the given list of photos. This is not an efficient
	 * approach to conflict resolution, but I'm betting that we do not have so many files that it will be a big problem for most computers
	 * to do in a short time.
	 * 
	 * @param photos a collection of photos.
	 * @param childAlbum a photo album to compare the photos with.
	 * @return return the list of conflicts. Returns an empty list when there are no conflicts.
	 */
	private static ArrayList<IPhotoFile> getConflicts(ArrayList<IPhotoFile> photos, IPhotoAlbum childAlbum) {
		ArrayList<IPhotoFile> conflicts = new ArrayList<IPhotoFile>();

		if (childAlbum.getPhotos().size() > 0 && photos.size() > 0) {
			for (IPhotoFile photoChild : childAlbum.getPhotos()) {
				for (IPhotoFile photoHere : photos) {
					// System.out.println("Parent: " + photoHere.getFileName() + "   Child:" + photoChild.getFileName());
					if (photoHere.getFileName().equalsIgnoreCase(photoChild.getFileName())) {
						conflicts.add(photoChild);
					}
				}
			}
		}

		return conflicts;
	}

	/**
	 * Looks at the album name and works out if it is one of the supported formats, returning the matching format if found.
	 * 
	 * @param file the file that is being checked to see if it is the right format.
	 * @return Returns a matching format, else null
	 */
	private static DateFormat isSupportedFormat(File file, ArrayList<DateFormat> formats) {

		for (DateFormat format : formats) {
			if (format.matches(file.getName()))
				return format;
		}

		return null;
	}

	/**
	 * Move the given file to the given destination
	 * 
	 * @param photo - The file being moved.
	 * @param destinationAlbum - The destination album.
	 * @return returns true if successful
	 */
	private static boolean movePhoto(IPhotoFile photo, IPhotoAlbum destinationAlbum) {
		if (!photo.isPhoto())
			throw new IllegalArgumentException("The \"photo\" must be a file. Don't get things confused.");
		if (!destinationAlbum.isAlbum())
			throw new IllegalArgumentException("The \"destinationAlbum\" must be a directory");
		if (!destinationAlbum.exists())
			throw new IllegalArgumentException("The \"destinationAlbum\" must exist");

		if (photo.move(destinationAlbum)) {
			destinationAlbum.getPhotos().add(photo);
			return true;
		}

		return false;
	}

	// Fields -----------------------------------------------------------------
	private ArrayList<IPhotoFile>	photos	= new ArrayList<IPhotoFile>();
	private LinkedList<IPhotoAlbum>	albums	= new LinkedList<IPhotoAlbum>();
	private ArrayList<DateFormat>	formats;									// TODO: This should not be created here. It may

	/**
	 * Root Album constructor
	 */
	public PhotoAlbum(File albumDir) {
		this(albumDir, null); // TODO Formats
	}

	// Constructors

	/**
	 * Regular album constructor.
	 * 
	 * @param albumDir - the directory of the album
	 * @param parentAlbum - A regular album requires a parent album
	 */
	public PhotoAlbum(File albumDir, PhotoAlbum parentAlbum) {
		super(albumDir, parentAlbum);

		DateFormat dateFormat = PhotoAlbum.isSupportedFormat(albumDir, createAlternateFormats());

		if (dateFormat != null) {
			initialiseDate(dateFormat);
		}

		refreshAlbumContents();
	}

	// Interface Methods ------------------------------------------------------

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#collapseAlbums()
	 */
	@Override
	public boolean collapseAlbums() {
		if (albums.size() != 0) {
			boolean success = true;
			for (Iterator<IPhotoAlbum> it = albums.iterator(); it.hasNext();) {
				IPhotoAlbum childAlbum = it.next();

				// System.out.println(childAlbum.getNameOriginal());
				// if the child has children of it's own, then recurse into that
				if (childAlbum.albumsCount() != 0) {
					childAlbum.collapseAlbums();
				}

				// after recursing to the deepest child album, move the files to here
				if (moveFilesHere(childAlbum)) {
					// if all went well, then delete the album.
					success &= childAlbum.deleteAlbum();
					if (success)
						it.remove();
					else
						System.out.println("Conflicts caused a failure to delete:\n\t" + childAlbum.getFileName());
				} else {
					success = false;
				}
			}

			if (success) {
				// if all went well, ditch our childAlbum because we shouldn't have any
				albums.clear();
			}
			return success;
		}
		return false;
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#albumsCount()
	 */
	@Override
	public int albumsCount() {
		return albums.size();
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#albumsCountRecursive()
	 */
	@Override
	public int albumsCountRecursive() {
		int count = albums.size();
		for (IPhotoAlbum childAlbum : albums) {
			count += childAlbum.albumsCountRecursive();
		}

		return count;
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#albumAt(int)
	 */
	@Override
	public IPhotoAlbum albumAt(int index) {
		return albums.get(index);
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#countPhotos()
	 */
	@Override
	public int photosCount() {
		return photos.size();
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#photosCountRecursive()
	 */
	@Override
	public int photosCountRecursive() {
		int count = photos.size();

		for (IPhotoAlbum album : albums) {
			count += album.photosCountRecursive();
		}

		return count;
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#photoAt(int)
	 */
	@Override
	public IPhotoFile photoAt(int index) {
		return photos.get(index);
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#deleteAlbum()
	 */
	@Override
	public boolean deleteAlbum() {
		if (!isAlbum())
			throw new IllegalArgumentException("album must be a directory");
		if (photosCount() != 0)
			throw new IllegalArgumentException("The Album being deleted must be empty");

		return getFile().delete();
	}

	/*
	 * (non-Javadoc)
	 * @see code.IDateBasedFile#getAlternateFormats()
	 */
	@Override
	public ArrayList<DateFormat> getAlternateFormats() {
		if (formats == null) {
			formats = createAlternateFormats();
		}

		return formats;
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#getChildAlbums()
	 */
	@Override
	public LinkedList<IPhotoAlbum> getChildAlbums() {
		return albums;
	}

	// POJO -------------------------------------------------------------------
	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#getPhotos()
	 */
	@Override
	public ArrayList<IPhotoFile> getPhotos() {
		return photos;
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#moveFilesHere()
	 */
	@Override
	public boolean moveFilesHere(IPhotoAlbum childAlbum) {
		boolean result = true; // as we go through, we'll look for the first error

		ArrayList<IPhotoFile> conflicts = getConflicts(getPhotos(), childAlbum);

		if (conflicts.size() != 0) {
			result = false;
		} else {
			for (IPhotoFile photo : childAlbum.getPhotos()) {
				if (!movePhoto(photo, this)) {
					result = false;
					break;
				}
			}

			childAlbum.refreshAlbumContents();
		}

		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#refreshAlbumContents()
	 */
	@Override
	public void refreshAlbumContents() {
		File[] children = getFile().listFiles();
		photos.clear();
		albums.clear();

		if (children != null) {
			for (File file : children) {
				if (file.isDirectory()) {
					addChildAlbum(file);
				} else {
					if (file.isFile() && file.getPath().matches("(?i).*.jpg")) {
						photos.add(new PhotoFile(file, this));
					}
				}
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#renamePhotosInAlbums()
	 */
	@Override
	public void renamePhotosInAlbums() {
		for (IPhotoFile photo : photos) {
			try {
				photo.rename("yyyyMMddUnder"); // TODO: Revise with new DateFormat
			}
			catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		// conduct a depth first search, by recursing to the deepest album, and renaming the photos there.
		for (IPhotoAlbum album : albums) {
			album.renamePhotosInAlbums();
		}

	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#setChildAlbums(java.util.LinkedList)
	 */
	@Override
	public void setChildAlbums(LinkedList<IPhotoAlbum> childAlbums) {
		this.albums = childAlbums;
	}

	/*
	 * (non-Javadoc)
	 * @see code.IPhotoAlbum#setPhotos(java.util.ArrayList)
	 */
	@Override
	public void setPhotos(ArrayList<IPhotoFile> photos) {
		this.photos = photos;
	}

	/**
	 * Add a child album to the list.
	 * 
	 * @param file
	 */
	private void addChildAlbum(File file) {
		albums.add(new PhotoAlbum(file, this));
	}
}
